﻿using System;
using System.Runtime.InteropServices;
/*
 * User: rsny
 * Date: 5/14/2012
 * Time: 2:24 PM
 */

namespace Aspects.PInvoke
{
    public partial class Native
    {
        private const uint OPEN_EXISTING = 0x03;
        private const uint FILE_SHARE_RW = 0x03;
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const uint GENERIC_READ_WRITE = GENERIC_READ | GENERIC_WRITE;
        private const uint DFP_RECEIVE_DRIVE_DATA = 0x7C088;
        private const uint SMART_GET_VERSION = 0x74080;
        private const byte IDE_ATAPI_IDENTIFY = 0xA1; // Returns ID sector for ATAPI.
        private const byte IDE_ATA_IDENTIFY = 0xEC; // Returns IDsector for ATA.
        
        private static IntPtr GetHddPointer()
        {
            return CreateFile(@"\\.\PhysicalDrive0", GENERIC_READ_WRITE, FILE_SHARE_RW, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
        }
        
        private static byte GetHddIdentifyMethod(IntPtr disk)
        {
            uint bytesReturned = 0;
            uint bufferSize = (uint)Marshal.SizeOf(typeof(GETVERSIONOUTPARAMS));
            IntPtr outBufferPointer = Marshal.AllocHGlobal((int)bufferSize);
            if (DeviceIoControl(disk, SMART_GET_VERSION, IntPtr.Zero, 0, outBufferPointer, bufferSize, out bytesReturned, IntPtr.Zero))
            {
                GETVERSIONOUTPARAMS outbuffer = (GETVERSIONOUTPARAMS)Marshal.PtrToStructure(outBufferPointer, typeof(GETVERSIONOUTPARAMS));
                Marshal.FreeHGlobal(outBufferPointer);
                return (outbuffer.bIDEDeviceMap & 0x10) == 0 ? IDE_ATA_IDENTIFY : IDE_ATAPI_IDENTIFY;
            }
            else
            {
                Marshal.FreeHGlobal(outBufferPointer);
                Console.WriteLine("Error Getting HDD Identity:{0}",Marshal.GetLastWin32Error());
                return 0x00;
            }
        }
        
        private static byte[] GetHddSmartData()
        {
            IntPtr disk = GetHddPointer();
            SENDCMDINPARAMS inBuffer = new SENDCMDINPARAMS();
            inBuffer.cBufferSize = (uint)Marshal.SizeOf(typeof(SENDCMDOUTPARAMS));
            inBuffer.bDriveNumber = 0;
            inBuffer.irDriveRegs.bFeaturesReg = 0;
            inBuffer.irDriveRegs.bSectorCountReg = 1;
            inBuffer.irDriveRegs.bSectorNumberReg = 1;
            inBuffer.irDriveRegs.bCylLowReg = 0;
            inBuffer.irDriveRegs.bCylHighReg = 0;
            inBuffer.irDriveRegs.bDriveHeadReg = 0xA0;
            inBuffer.irDriveRegs.bCommandReg = GetHddIdentifyMethod(disk);
            
            int inBufferSize = Marshal.SizeOf(typeof(SENDCMDINPARAMS));
            int outBufferSize = Marshal.SizeOf(typeof(SENDCMDOUTPARAMS));
            IntPtr inBufferPointer = Marshal.AllocHGlobal((int)inBuffer.cBufferSize);
            IntPtr outBufferPointer = Marshal.AllocHGlobal(outBufferSize);
            uint bytesReturned;
            Marshal.StructureToPtr(inBuffer, inBufferPointer, true);
            
            DeviceIoControl(disk, DFP_RECEIVE_DRIVE_DATA, inBufferPointer, (uint)inBufferSize, outBufferPointer, (uint)outBufferSize, out bytesReturned, IntPtr.Zero);
            
            SENDCMDOUTPARAMS outBuffer = (SENDCMDOUTPARAMS)Marshal.PtrToStructure(outBufferPointer, typeof(SENDCMDOUTPARAMS));
            
            Marshal.FreeHGlobal(inBufferPointer);
            Marshal.FreeHGlobal(outBufferPointer);
            CloseHandle(disk);

            return outBuffer.bBuffer;
        }
        
        #region PInvoke
        /// <summary>
        /// Creates or opens a file or I/O device
        /// </summary>
        /// <param name="lpFileName">The name of the file or device to be created or opened. To create a file stream, specify the name of the file, a colon, and then the name of the stream</param>
        /// <param name="dwDesiredAccess">The requested access to the file or device, which can be summarized as read, write, both or neither zero</param>
        /// <param name="dwShareMode">The requested sharing mode of the file or device, which can be read, write, both, delete, all of these, or none </param>
        /// <param name="lpSecurityAttributes">A pointer to a SECURITY_ATTRIBUTES structure that contains two separate but related Data members: an optional security descriptor, and a Boolean value that determines whether the returned handle can be inherited by child processes.</param>
        /// <param name="dwCreationDisposition">An action to take on a file or device that exists or does not exist. For devices other than files, this parameter is usually set to OPEN_EXISTING</param>
        /// <param name="dwFlagsAndAttributes">The file or device attributes and flags, FILE_ATTRIBUTE_NORMAL being the most common default value for files</param>
        /// <param name="hTemplateFile"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess,uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,uint dwFlagsAndAttributes, IntPtr hTemplateFile);

        /// <summary>
        /// Sends a control code directly to a specified device driver, causing the corresponding device to perform the corresponding operation
        /// </summary>
        /// <param name="hDevice">A handle to the device on which the operation is to be performed. </param>
        /// <param name="dwIoControlCode">The control code for the operation. This value identifies the specific operation to be performed and the type of device on which to perform it. For a list of the control codes, see Remarks. The documentation for each control code provides usage details for the lpInBuffer, nInBufferSize, lpOutBuffer, and nOutBufferSize parameters.</param>
        /// <param name="lpInBuffer">A pointer to the input buffer that contains the Data required to perform the operation. The format of this Data depends on the value of the dwIoControlCode parameter. </param>
        /// <param name="nInBufferSize">The size of the input buffer, in bytes</param>
        /// <param name="lpOutBuffer">A pointer to the output buffer that is to receive the Data returned by the operation</param>
        /// <param name="nOutBufferSize">The size of the output buffer, in bytes</param>
        /// <param name="lpBytesReturned">A pointer to a variable that receives the size of the Data stored in the output buffer, in bytes</param>
        /// <param name="lpOverlapped">A pointer to an OVERLAPPED structure.</param>
        /// <returns></returns>
        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,[In] IntPtr lpInBuffer, uint nInBufferSize,[Out] IntPtr lpOutBuffer, uint nOutBufferSize,out uint lpBytesReturned, IntPtr lpOverlapped);
        #endregion
        
        #region Structures
        
        [StructLayout(LayoutKind.Sequential)]
        private struct GETVERSIONOUTPARAMS
        {
            public byte bVersion;
            public byte bRevision;
            public byte bReserved;
            public byte bIDEDeviceMap;
            public uint fCapabilities;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            private uint[] dwReserved;
        }
        
        [StructLayout(LayoutKind.Sequential)]
        private struct IDEREGS
        {
            public byte bFeaturesReg;//;
            public byte bSectorCountReg;//;
            public byte bSectorNumberReg;//;
            public byte bCylLowReg;//;
            public byte bCylHighReg;//;
            public byte bDriveHeadReg;//;
            public byte bCommandReg;//;
            public byte bReserved;//;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct SENDCMDINPARAMS
        {
            public uint cBufferSize;
            public IDEREGS irDriveRegs;
            public byte bDriveNumber;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public byte[] bReserved;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public uint[] dwReserved;
            public byte bBuffer;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct DRIVERSTATUS
        {
            public byte bDriveError;//;
            public byte bIDEStatus; //;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public byte[] bReserved; //(1 To 2);
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public uint[] dwReserved; //(1 To 2) As Long
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct SENDCMDOUTPARAMS
        {
            public uint cBufferSize; // As Long
            public DRIVERSTATUS DStatus;// As DRIVERSTATUS
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
            public byte[] bBuffer; //(1 To 512);
        }
        
        #endregion
    }
}